package Modelo;

import java.io.*;

public class SHA1 {

    private int[] bitlen = new int[2];
    private int[] var = new int[0x15];
    private static  String reporte = "";

    //Funcion auxiliar
    public static int[] hash(byte[] data) {
        return SHA1.hash(byteArrayToCharArray(data));
    }

    //Funcion prinpipal para sacar sha1
    public static int[] hash(char[] data) {
                
        SHA1 ctx = new SHA1();
        ctx.actualizar(data);
        return ctx.Construir();
    }

    //Funcion auxiliar
    public static int[] hash(String data) {
        reporte += reporte + "\n\nReporte de SHA1\n\n";
        reporte += reporte + "Texto a hashear: "+data+"\n\n";
        return SHA1.hash(data.toCharArray());
    }

    //Constructor
    public SHA1() {
        //Valores iniciales
        bitlen[0] = 0;
        bitlen[1] = 0;
        /*vector var cn valores inicializados; requeridos por el algoritmo*/
        var[0] = 0x67452301;
        var[1] = 0xEFCDAB89;
        var[2] = 0x98BADCFE;
        var[3] = 0x10325476;
        var[4] = 0xC3D2E1F0;

        reporte = "";
    }

    //Deja el bytes en BigEndian
    private static int EndianInverso(int i) {
        i = ((i << 24) & 0xFF000000)
                | ((i << 8) & 0x00FF0000)
                | ((i >> 8) & 0x0000FF00)
                | ((i >> 24) & 0x000000FF);

        return i;
    }

    //Ultimo paso para concatenar las variables y para dejar en Bigendian
    public int[] Construir() {
        byte[] vars;
        int len;
        char[] MBuffer;
        int[] temp_vars = new int[2];

        temp_vars[0] = SHA1.EndianInverso(bitlen[1]);
        temp_vars[1] = SHA1.EndianInverso(bitlen[0]);

        len = ((-9 - (bitlen[0] >>> 3)) & 0x3F) + 1;

        vars = (new ByteDesdetArray(true)).getByteArray(temp_vars);
        MBuffer = new char[len];

        MBuffer[0] = (char) 0x80;
        for (int x = 1; x < len; x++) {
            MBuffer[x] = (char) 0;
        }

        actualizar(MBuffer);
        actualizar(byteArrayToCharArray(vars));

        int[] hash = new int[5];

        //Se recuperan los 5 trozos de los state
        for (int x = 0; x < 5; x++) {
            hash[x] = var[x];
        }

        String STRhash = String.format("%08x ",hash[0]) + String.format("%08x ",hash[1]) + String.format("%08x ",hash[2]) + String.format("%08x ",hash[3]) + String.format("%08x ",hash[4]);
//        reporte += reporte + "**** Union y fin del algoritmo ***\n\n";
//        reporte += reporte + "Hash final: "+ STRhash+"\n\n";

        return hash;
    }

    //Funcion auxiliar, pasa del arreglo de byte a char para el update
    public void actualizar(byte[] data) {
        this.actualizar(SHA1.byteArrayToCharArray(data));
    }

    //Pre Procesado y actualizado
    public void actualizar(char[] data) {
//        reporte += reporte + "Actualizando datos(BigEndian) para luego transformarlos\n";
        int a = 0, b = 0, c = 0, x = 0, len = data.length;
        c = len >> 29;
        b = len << 3;

        a = (bitlen[0] / 8) & 0x3F;

        if (bitlen[0] + b < bitlen[0] || bitlen[0] + b < b) {
            bitlen[1]++;
        }
        bitlen[0] += b;
        bitlen[1] += c;

        len += a;
        x = -a;
        ByteDesdetArray bda = new ByteDesdetArray(true);

        if (len >= 0x40) {
            if (a > 0) {
                while (a < 0x40) {
                    bda.insertarByte(var, a + 0x14, (byte) data[a + x]);
                    a++;
                }
                transformar(var);
                len -= 0x40;
                x += 0x40;
                a = 0;
            }
            if (len >= 0x40) {
                b = len;
                for (int i = 0; i < b / 0x40; i++) {
                    for (int y = 0; y < 0x40; y++) {
                        bda.insertarByte(var, y + 0x14, (byte) data[x + y]);
                    }
                    transformar(var);
                    len -= 0x40;
                    x += 0x40;
                }
            }
        }
        while (a < len) {
            bda.insertarByte(var, 20 + a, (byte) data[a + x]);
            a++;
        }
        return;
    }

    //Loop principal del algoritmo
    private static void transformar(int[] hashBuffer) {
//        reporte += reporte + "===Incio de ciclo de transformado===\n\n";
        int buf[] = new int[0x50];
        int dw, a, b, c, d, e, p, i;

        for (i = 5; i < hashBuffer.length; i++) {
            hashBuffer[i] = SHA1.EndianInverso(hashBuffer[i]);
        }

        for (i = 0; i < 0x10; i++) {
            buf[i] = hashBuffer[i + 5];
        }

        for (i = 0; i < 0x40; i++) {
            dw = buf[i + 13] ^ buf[i + 8] ^ buf[i + 0] ^ buf[i + 2];
            buf[i + 16] = (dw >>> 0x1f) | (dw << 1);
        }

//        reporte += reporte + "valores iniciales: \n\n";

        a = hashBuffer[0];
//        reporte += reporte + "a = " + a +"\n";
        b = hashBuffer[1];
//        reporte += reporte + "b = " + b+"\n";
        c = hashBuffer[2];
//        reporte += reporte + "c = " + c+"\n";
        d = hashBuffer[3];
//        reporte += reporte + "d = " + d+"\n";
        e = hashBuffer[4];
//        reporte += reporte + "e = " + e+"\n";

        p = 0;

        i = 0x14;
        do {
            dw = ((a << 5) | (a >>> 0x1b)) + ((~b & d) | (c & b)) + e + buf[p++] + 0x5a827999;
            e = d;
            d = c;
            c = (b >>> 2) | (b << 0x1e);
            b = a;
            a = dw;
        } while (--i > 0);

        i = 0x14;
        do {
            dw = (d ^ c ^ b) + e + ((a << 5) | (a >>> 0x1b)) + buf[p++] + 0x6ED9EBA1;
            e = d;
            d = c;
            c = (b >>> 2) | (b << 0x1e);
            b = a;
            a = dw;
        } while (--i > 0);

        i = 0x14;
        do {
            dw = ((c & b) | (d & c) | (d & b)) + e + ((a << 5) | (a >>> 0x1b)) + buf[p++] - 0x70E44324;
            e = d;
            d = c;
            c = (b >>> 2) | (b << 0x1e);
            b = a;
            a = dw;
        } while (--i > 0);

        i = 0x14;
        do {
            dw = ((a << 5) | (a >>> 0x1b)) + e + (d ^ c ^ b) + buf[p++] - 0x359D3E2A;
            e = d;
            d = c;
            c = (b >>> 2) | (b << 0x1e);
            b = a;
            a = dw;
        } while (--i > 0);

//        reporte += reporte + "valores Finales: \n\n";

        hashBuffer[0] += a;
//        reporte += reporte + "a = " + hashBuffer[0] +"\n";
        hashBuffer[1] += b;
//        reporte += reporte + "b = " + hashBuffer[1]+"\n";
        hashBuffer[2] += c;
//        reporte += reporte + "c = " + hashBuffer[2]+"\n";
        hashBuffer[3] += d;
//        reporte += reporte + "d = " + hashBuffer[3]+"\n";
        hashBuffer[4] += e;
//        reporte += reporte + "e = " + hashBuffer[4]+"\n";

//        reporte += reporte + "+++Fin de ciclo de transformado+++\n\n";
    }

    //Funcion para sacar el hash a un archivo
    public boolean hash_archivo(String filename) {
        try {
            byte[] data = new byte[(int) (new File(filename)).length()];
            InputStream in = new FileInputStream(filename);
            in.read(data);
            in.close();
            actualizar(byteArrayToCharArray(data));
        } catch (Exception e) {
            System.out.println("No se pudo completar el hash(" + filename + ") Fallo: " + e.toString());
            return false;
        }
        return true;
    }

    //Pasa un array de bytes a chars
    private static char[] byteArrayToCharArray(byte[] a) {
        char[] buff = new char[a.length];
        for (int x = 0; x < a.length; x++) {
            buff[x] = (char) (a[x] & 0x000000FF);
        }
        return buff;
    }

    public static String getReporte() {
        return reporte;
    }

    public static void setReporte(String reporte) {
        SHA1.reporte = reporte;
    }

    

}
